/*
 * $Id: StringUtil.java 9 2006-03-08 10:21:59Z wjx $
 */
package com.someok.utils;

/**
 * General string utils.
 *
 * <p><a href="StringUtil.java.html"><i>View Source</i></a></p>
 *
 * @version $Revision: 9 $ $Date: 2006-03-08 18:21:59 +0800 (星期三, 08 三月 2006) $
 */
public final class StringUtil {

	// ---------------------------------------------------------------- replace

	/**
	 * Replaces the occurences of a certain pattern in a string with a
	 * replacement String. This is the fastest replace function known to author.
	 *
	 * @param s      the string to be inspected
	 * @param sub    the string pattern to be replaced
	 * @param with   the string that should go where the pattern was
	 *
	 * @return the string with the replacements done
	 */
	public static String replace(String s, String sub, String with) {
		if ((s == null) || (sub == null) || (with == null)) {
			return s;
		}
		int c = 0;
		int i = s.indexOf(sub, c);
		if (i == -1) {
			return s;
		}
		StringBuffer buf = new StringBuffer(s.length() + with.length());
		do {
			 buf.append(s.substring(c,i));
			 buf.append(with);
			 c = i + sub.length();
		 } while ((i = s.indexOf(sub, c)) != -1);
		 if (c < s.length()) {
			 buf.append(s.substring(c,s.length()));
		 }
		 return buf.toString();
	}

	/**
	 * Character replacement in a string. All occurrencies of a character will be
	 * replaces.
	 *
	 * @param s      input string
	 * @param sub    character to replace
	 * @param with   character to replace with
	 *
	 * @return string with replaced characters
	 */
	public static String replace(String s, char sub, char with) {
		if (s == null) {
			return s;
		}
		char[] str = s.toCharArray();
		for (int i = 0; i < str.length; i++) {
			if (str[i] == sub) {
				str[i] = with;
			}
		}
		return new String(str);
	}

	/**
	 * Replaces the very first occurance of a substring with suplied string.
	 *
	 * @param s      source string
	 * @param sub    substring to replace
	 * @param with   substring to replace with
	 *
	 * @return modified source string
	 */
	public static String replaceFirst(String s, String sub, String with) {
		if ((s == null) || (sub == null) || (with == null)) {
			return s;
		}
		int i = s.indexOf(sub);
		if (i == -1) {
			return s;
		}
		return s.substring(0, i) + with + s.substring(i + sub.length());
	}

	/**
	 * Replaces the very first occurence of a character in a String.
	 *
	 * @param s      string
	 * @param sub    char to replace
	 * @param with   char to replace with
	 *
	 * @return modified string
	 */
	public static String replaceFirst(String s, char sub, char with) {
		if (s == null) {
			return s;
		}
		char[] str = s.toCharArray();
		for (int i = 0; i < str.length; i++) {
			if (str[i] == sub) {
				str[i] = with;
				break;
			}
		}
		return new String(str);
	}

	/**
	 * Replaces the very last  occurance of a substring with suplied string.
	 *
	 * @param s      source string
	 * @param sub    substring to replace
	 * @param with   substring to replace with
	 *
	 * @return modified source string
	 */
	public static String replaceLast(String s, String sub, String with) {
		if ((s == null) || (sub == null) || (with == null)) {
			return s;
		}
		int i = s.lastIndexOf(sub);
		if (i == -1) {
			return s;
		}
		return s.substring(0, i) + with + s.substring(i + sub.length());
	}

	/**
	 * Replaces the very last occurence of a character in a String.
	 *
	 * @param s      string
	 * @param sub    char to replace
	 * @param with   char to replace with
	 *
	 * @return modified string
	 */
	public static String replaceLast(String s, char sub, char with) {
		if (s == null) {
			return s;
		}
		char[] str = s.toCharArray();
		for (int i = str.length - 1; i >= 0; i--) {
			if (str[i] == sub) {
				str[i] = with;
				break;
			}
		}
		return new String(str);
	}

	// ---------------------------------------------------------------- misc
	/**
	 * Compares 2 strings. If one of the strings is null, false is returned. if
	 * both string are null, true is returned.
	 *
	 * @param s1     first string to compare
	 * @param s2     second string
	 *
	 * @return true if strings are equal, otherwise false
	 */
	public static boolean equals(String s1, String s2) {
		return Util.equals(s1, s2);
	}

	/**
	 * Determines if a string is empty. If string is NULL, it returns true.
	 *
	 * @param s      string
	 *
	 * @return true if string is empty or null.
	 */
	public static boolean isEmpty(String s) {
		if (s != null) {
			return (s.length() == 0);
		}
		return true;
	}

	/**
	 * Set the maximum length of the string. If string is longer, it will be
	 * shorten.
	 *
	 * @param s      string
	 * @param len    max number of characters in string
	 *
	 * @return string with length no more then specified
	 */
	public static String setMaxLength(String s, int len) {
		if (s == null) {
			return s;
		}
		if (s.length() > len) {
			s = s.substring(0, len);
		}
		return s;
	}

	/**
	 * Converts an object to a String. If object is <code>null</code> it will be
	 * not converted.
	 *
	 * @param obj    object to convert to string
	 *
	 * @return string created from the object or <code>null</code>
	 */
	public static String toString(Object obj) {
		if (obj == null) {
			return (String)null;
		}
		return obj.toString();
	}


	/**
	 * Test if the input string is null or empty (has 0 characters)
	 *
	 * @param s
	 *            the input string to test
	 * @return true if the input string is null; false otherwise
	 */
	public static boolean isNull(String s) {
		return ((s == null) || (s.length() < 1));
	}

	/**
	 * Test if the input string is null or empty or if it's equal to the input
	 * <code>val</code> parameter.
	 *
	 * @param s
	 *            the input string to test
	 * @param val
	 *            the value string to compare to <code>s</code>
	 * @return true if <code>s</code> is null, or empty, or if it's equal to
	 *         the <code>val</code> string; false otherwise.
	 */
	public static boolean isNull(String s, String val) {
		return (isNull(s) || (s.compareTo(val) == 0));
	}

	/**
	 * Converts an object to a String. If object is <code>null</code> a empty
	 * string is returned.
	 *
	 * @param obj    object to convert to string
	 *
	 * @return string created from the object
	 */
	public static String nvl(Object obj) {
		if (obj == null) {
			return "";
		}
		return obj.toString();
	}


	/**
	 * If param is null, then return "", else return the param.
	 *
	 * @param s the input string
	 * @return
	 */
	public static String nvl(String s) {
		return nvl(s, "");
	}

	/**
	 * If param is null, then return default value, else return the param.
	 * @param s
	 * @param defaultValue default value
	 * @return
	 */
	public static String nvl(String s, String defaultValue) {
		return (s == null) ? defaultValue : s;
	}

	/**
	 * Delimit the input string with single quote characters (').
	 *
	 * @param v
	 *            The new string value
	 * @return the input string delimited with single quote characters;
	 */
	public static String dbString(String v) {
		StringBuffer sb = new StringBuffer();

		return (isNull(v) ? "" : (sb.append("'").append(v).append("'")
				.toString()));
	}

	/**
	 * Converts an object to a String Array.
	 *
	 * @param obj    object to convert to string array
	 *
	 * @return string array created from the object
	 */
	public static String[] toStringArray(Object obj) {
		if (obj == null) {
			return (String[]) null;
		}

		// handle arrays
		if (obj.getClass().isArray() == true) {
			if (obj instanceof String[]) {
				return (String[]) obj;
			} else {
				Object[] valueArray = (Object[]) obj;
				String[] result = new String[valueArray.length];
				for (int i = 0; i < valueArray.length; i++) {
					result[i] = valueArray[i].toString();
				}
				return result;
			}
		}
		// handle all other values
		return new String[] { obj.toString() };
	}
	// ---------------------------------------------------------------- split

	/**
	 * Splits a string in several parts (tokens) that are separated by delimeter.
	 * Delimeter is <b>always</b> surrounded by two strings! If there is no
	 * content between two delimeters, empty string will be returned for that
	 * token. Therefore, the length of the returned array will always be:
	 * #delimeters + 1.<br><br>
	 *
	 * Method is much, much faster then regexp <code>String.split()</code>,
	 * and a bit faster then <code>StringTokenizer</code>.
	 *
	 * @param src       string to split
	 * @param delimeter split delimeter
	 *
	 * @return array of splitted strings
	 */
	public static String[] split(String src, String delimeter) {
		if (src == null) {
			return null;
		}
		if (delimeter == null) {
			return new String[] {src};
		}
		int maxparts = (src.length() / delimeter.length()) + 2;		// one more for the last
		int[] positions = new int[maxparts];
		int dellen = delimeter.length();

		int i = 0, j = 0;
		int count = 0;
		positions[0] = - dellen;
		while ((i = src.indexOf(delimeter, j)) != -1) {
			count++;
			positions[count] = i;
			j = i + dellen;
		}
		count++;
		positions[count] = src.length();

		String[] result = new String[count];

		for (i = 0; i < count; i++) {
			result[i] = src.substring(positions[i] + dellen, positions[i + 1]);
		}
		return result;
	}

	/**
	 * Splits a string in several parts (tokens) that are separated by deliemter
	 * characters. Deliemter may contains any number of character, and it is
	 * always surrounded by two strings.
	 *
	 * @param src    source to examine
	 * @param d      string with delimeter characters
	 *
	 * @return array of tokens
	 */
	public static String[] splitc(String src, String d) {
		if (src == null) {
			return null;
		}
		if ((d == null) || (d.length() == 0) || (src.length() == 0) ) {
			return new String[] {src};
		}
		char[] delimeters = d.toCharArray();
		char[] srcc = src.toCharArray();

		int maxparts = srcc.length + 1;
		int[] start = new int[maxparts];
		int[] end = new int[maxparts];

		int count = 0;

		start[0] = 0;
		int s = 0, e;
		if (CharUtil.equals(srcc[0], delimeters) == true) {	// string starts with delimeter
			end[0] = 0;
			count++;
			s = CharUtil.findFirstDiff(srcc, 1, delimeters);
			if (s == -1) {							// nothing after delimeters
				return new String[] {"", ""};
			}
			start[1] = s;							// new start
		}
		while (true) {
			// find new end
			e = CharUtil.findFirstAny(srcc, s, delimeters);
			if (e == -1) {
				end[count] = srcc.length;
				break;
			}
			end[count] = e;

			// find new start
			count++;
			s = CharUtil.findFirstDiff(srcc, e, delimeters);
			if (s == -1) {
				start[count] = end[count] = srcc.length;
				break;
			}
			start[count] = s;
		}
		count++;
		String[] result = new String[count];
		for (int i = 0; i < count; i++) {
			result[i] = src.substring(start[i], end[i]);
		}
		return result;
	}




	// ---------------------------------------------------------------- byte arrays

	/**
	 * Converts string into byte array. Chars are truncated to byte size.
	 *
	 * @param s      string to convert from
	 *
	 * @return byte array
	 */
	public static byte[] toByteArray(String s) {
		if (s == null) {
			return null;
		}
		return CharUtil.toByteArray(s.toCharArray());
	}

	// ---------------------------------------------------------------- ignore cases


	/**
	 * Finds first index of a substring in the given source string with ignored case.
	 *
	 * @param src    source string for examination
	 * @param subS   substring to find
	 *
	 * @return index of founded substring or -1 if substring is not found
	 * @see #indexOfIgnoreCase(String, String, int)
	 */
	public static int indexOfIgnoreCase(String src, String subS) {
		return indexOfIgnoreCase(src, subS, 0);
	}

	/**
	 * Finds first index of a substring in the given source string with ignored
	 * case. This seems to be the fastest way doing this, with common string
	 * length and content (of course, with no use of Boyer-Mayer type of
	 * algorithms). Other implementations are slower: getting char array frist,
	 * lowercasing the source string, using String.regionMatch etc.
	 *
	 * @param src        source string for examination
	 * @param subS       substring to find
	 * @param startIndex starting index from where search begins
	 *
	 * @return index of founded substring or -1 if substring is not found
	 */
	public static int indexOfIgnoreCase(String src, String subS, int startIndex) {
		String sub = subS.toLowerCase();
		int sublen = sub.length();
		int total = src.length() - sublen + 1;
		for (int i = startIndex; i < total; i++) {
			int j = 0;
			while (j < sublen) {
				char source = Character.toLowerCase(src.charAt(i + j));
				if (sub.charAt(j) != source) {
					break;
				}
				j++;
			}
			if (j == sublen) {
				return i;
			}
		}
		return -1;
	}


	/**
	 * Finds last index of a substring in the given source string with ignored
	 * case.
	 *
	 * @param s
	 * @param subS   substring to find
	 *
	 * @return last index of founded substring or -1 if substring is not found
	 * @see #indexOfIgnoreCase(String, String, int)
	 * @see #lastIndexOfIgnoreCase(String, String, int)
	 */
	public static int lastIndexOfIgnoreCase(String s, String subS) {
		return lastIndexOfIgnoreCase(s, subS, 0);
	}

	/**
	 * Finds last index of a substring in the given source string with ignored
	 * case.
	 *
	 * @param src        source string for examination
	 * @param subS       substring to find
	 * @param startIndex starting index from where search begins
	 *
	 * @return last index of founded substring or -1 if substring is not found
	 * @see #indexOfIgnoreCase(String, String, int)
	 */
	public static int lastIndexOfIgnoreCase(String src, String subS, int startIndex) {
		String sub = subS.toLowerCase();
		int sublen = sub.length();
		int total = src.length() - sublen;
		for (int i = total; i >= startIndex; i--) {
			int j = 0;
			while (j < sublen) {
				char source = Character.toLowerCase(src.charAt(i + j));
				if (sub.charAt(j) != source) {
					break;
				}
				j++;
			}
			if (j == sublen) {
				return i;
			}
		}
		return -1;
	}

	/**
	 * Tests if this string starts with the specified prefix with ignored case.
	 *
	 * @param src    source string to test
	 * @param subS   starting substring
	 *
	 * @return <code>true</code> if the character sequence represented by the argument is
	 *         a prefix of the character sequence represented by this string;
	 *         <code>false</code> otherwise.
	 */
	public static boolean startsWithIgnoreCase(String src, String subS) {
		return startsWithIgnoreCase(src, subS, 0);
	}

	/**
	 * Tests if this string starts with the specified prefix with ignored case
	 * and with the specified prefix beginning a specified index.
	 *
	 * @param src        source string to test
	 * @param subS       starting substring
	 * @param startIndex index from where to test
	 *
	 * @return <code>true</code> if the character sequence represented by the argument is
	 *         a prefix of the character sequence represented by this string;
	 *         <code>false</code> otherwise.
	 */
	public static boolean startsWithIgnoreCase(String src, String subS, int startIndex) {
		String sub = subS.toLowerCase();
		int sublen = sub.length();
		if (startIndex + sublen > src.length()) {
			return false;
		}
		int j = 0;
		int i = startIndex;
		while (j < sublen) {
			char source = Character.toLowerCase(src.charAt(i));
			if (sub.charAt(j) != source) {
				return false;
			}
			j++; i++;
		}
		return true;
	}

	/**
	 * Tests if this string ends with the specified suffix.
	 *
	 * @param src    String to test
	 * @param subS   suffix
	 *
	 * @return <code>true</code> if the character sequence represented by the argument is
	 *         a suffix of the character sequence represented by this object;
	 *         <code>false</code> otherwise.
	 */
	public static boolean endsWithIgnoreCase(String src, String subS) {
		String sub = subS.toLowerCase();
		int sublen = sub.length();
		int j = 0;
		int i = src.length() - sublen;
		if (i < 0) {
			return false;
		}
		while (j < sublen) {
			char source = Character.toLowerCase(src.charAt(i));
			if (sub.charAt(j) != source) {
				return false;
			}
			j++; i++;
		}
		return true;
	}


	// ---------------------------------------------------------------- wildcard match


	/**
	 * Checks whether a string matches a given wildcard pattern.
	 * Possible patterns allow to match single characters ('?') or any count of
	 * characters ('*'). Wildcard characters can be escaped (by an '\').
	 *
	 * <p>This method uses recursive matching, as in linux or windows. regexp works the same.
	 * This method is very fast, comparing to similar implementations.
	 *
	 * @param string	input string
	 * @param pattern	pattern to match
	 * @return 			<code>true</code> if string matches the pattern, otherwise <code>fasle</code>
	 */
	public static final boolean match(String string, String pattern) {
		return match(string, pattern, 0, 0);
	}

	/**
	 * Internall matching recursive function.
	 */
	private static boolean match(String string, String pattern, int stringStartNdx, int patternStartNdx) {
		int pNdx = patternStartNdx;
		int sNdx = stringStartNdx;
		int pLen = pattern.length();
		int sLen = string.length();
		boolean nextIsNotWildcard = false;
		while (true) {

			// check if end of string and/or pattern occured
			if ((sNdx >= sLen) == true) {		// end of string still may have pending '*' in pattern
				while ((pNdx < pLen) && (pattern.charAt(pNdx) == '*')) {
					pNdx++;
				}
                if (pNdx >= pLen) {				// end of both string and pattern
					return true;
				}
				return false;
			}
			if (pNdx >= pLen) {					// end pf pattern, but not end of the string
				return false;
			}
			char p = pattern.charAt(pNdx);		// pattern char

			// perform logic
			if (nextIsNotWildcard == false) {

				if (p == '\\') {
					pNdx++;
					nextIsNotWildcard =  true;
					continue;
				}
				if (p == '?') {
					sNdx++; pNdx++;
					continue;
				}
				if (p == '*') {
					char pnext = 0;						// next pattern char
					if (pNdx + 1 < pLen) {
						pnext = pattern.charAt(pNdx + 1);
					}
					if (pnext == '*') {					// double '*' have the same effect as one '*'
						pNdx++;
						continue;
					}
					int i;
					pNdx++;

					// find recursively if there is any substring from the end of the
					// line that matches the rest of the pattern !!!
					for (i = string.length(); i >= sNdx; i--) {
						if (match(string, pattern, i, pNdx) == true) {
							return true;
						}
					}
					return false;
				}
			} else {
				nextIsNotWildcard = false;
			}

			// check if pattern char and string char are equals
			if (p != string.charAt(sNdx)) {
				return false;
			}

			// everything matches for now, continue
			sNdx++; pNdx++;
		}
	}

	// ---------------------------------------------------------------- count substrings

	/**
	 * Count substring occurences in a source string.
	 *
	 * @param source	source string
	 * @param sub		substring to count
	 * @return			number of substring occurences
	 */
	public final static int count(String source, String sub) {
		int count = 0;
		int i = 0, j = 0;
		while (true) {
			i = source.indexOf(sub, j);
			if (i == -1) {
				break;
			}
			count++;
			j = i + sub.length();
		}
		return count;
	}

	/**
	 * Count substring occurences in a source string, ignoring case.
	 *
	 * @param source	source string
	 * @param sub		substring to count
	 * @return			number of substring occurences
	 */
	public final static int countIgnoreCase(String source, String sub) {
		int count = 0;
		int i = 0, j = 0;
		while (true) {
			i = StringUtil.indexOfIgnoreCase(source, sub, j);
			if (i == -1) {
				break;
			}
			count++;
			j = i + sub.length();
		}
		return count;
	}


}
